﻿/*
 * Telnet.java
 *
 * Created on 2004年1月1日, 上午 5:47
 *
 */

package org.uranus.core.telnet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import org.uranus.core.terminal.ANSI;
import org.uranus.core.terminal.Terminal;

/**
 * Copyright 2007 Hsiang-Yu Wang<br>
 * This file is distributed under the terms of the GNU General Public License<br>
 *<br>
 *<pre>
 * --------------------License notice--------------------
 * GNU GENERAL PUBLIC LICENSE
 * Version 3, 29 June 2007
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not,
 * visit: http://www.gnu.org/licenses/gpl.txt
 * --------------------License notice--------------------</pre>
 *
 * @author Hsiang-Yu Wang
 */
/**
 * This is a basic Telnet protocol operator, including basic I/O operation.
 *
 *
 *
 *  @see Terminal
 */
public class Telnet extends Thread{
    private StreamConnection sc;
    private InputStream is;
    private OutputStream os;
    private String ip;
    private boolean flag;
    private ANSI buf;
    
    private byte[] payload;
    
    private int offset;
    private int state;
    
    private final byte IAC = (byte)255;
    
    private final byte DONT = (byte)254;
    private final byte DO = (byte)253;
    private final byte WONT = (byte)252;
    private final byte WILL = (byte)251;
    private final byte SB = (byte)250;
    private final byte EOR = (byte)239;
    private final byte SE = (byte)240;
    
    /** Telent option code: BINARY **/
    private final byte TELOPT_BINARY = (byte)0;
    /** Telent option code: ECHO **/
    private final byte TELOPT_ECHO = (byte)1;
    /** Telent option code: Suppress Go Ahead **/
    private final byte TELOPT_SGA = (byte)3;
    /** Telent option code: EOR **/
    private final byte TELOPT_EOR = (byte)25;
    /** Telent option code: Negotiate About Window Size **/
    private final byte TELOPT_NAWS = (byte)31;
    /** Telent option code: Terminal Type **/
    private final byte TELOPT_TTYPE = (byte)24;
    
    private final byte TELOPT_IS = (byte)0;
    private final byte TELOPT_SEND = (byte)1;
    
    /** init. terminal object from GUI.getCurrentTerminal
     */
    public Telnet(){
        buf = new ANSI();
    }
    
    
    public void setIP(String ip, int port){
        this.ip = "socket://"+ip+":"+port;
    }
    
    public void run(){
        try {
            
            sc = (StreamConnection)Connector.open(ip);
            os = sc.openOutputStream();
            is = sc.openInputStream();
            
            sendCmd(new byte[]{IAC,WILL,TELOPT_TTYPE});
            
            while(true){
                if(is.available() > 0){
                    payload = new byte[is.available()];
                    offset = 0;
                    while(is.available() > 0 && offset < payload.length){                      
                        payload[offset++] = dataRcv();
                    }
                    offset = 0;
                    while(offset < payload.length && payload[offset] == IAC){
                        iac(payload[++offset]);
                        offset++;
                    }
                    if(offset < payload.length){
                        byte[] tempPayload = new byte[payload.length - offset];
                        System.arraycopy(payload, offset, tempPayload, 0, tempPayload.length);
                        buf.startingFlush(tempPayload);
                        buf.clearBuffer();
                    }                    
                }
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            ex.printStackTrace();
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        
    }
    public boolean connectionAlive(){
        if(is != null && os != null){
            return true;
        }else{
            return false;
        }
    }
    public void sendCmd(byte[] cmd){
        try {
            os.write(cmd);
            os.flush();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    public byte dataRcv() throws IOException{
        int inInt = is.read();
        if(inInt == -1){
            is.close();
            os.close();
            sc.close();
            this.interrupt();
        }
        return (byte)inInt;
    }
    /**
     *   state: 0: null
     *          1: will
     *          2: do
     *
     *        255: SB end
     **/
    
    
    /** telnet option code handler
     *  @param cmd
     *         the option code of telnet protocol
     **/
    private void telopt(byte cmd) throws ArrayIndexOutOfBoundsException{
        switch(cmd){
            case (byte)TELOPT_BINARY:
                break;
            case (byte)TELOPT_ECHO:
                if(state == 1){
                    state = 0;
                    sendCmd(new byte[] {IAC, DONT, TELOPT_ECHO});
                }
                break;
            case (byte)TELOPT_SGA:
                break;
            case (byte)TELOPT_NAWS:
                if (state == 2){
                    sendCmd(new byte[] {IAC, WILL, TELOPT_NAWS});
                    sendCmd(new byte[] {IAC,SB,TELOPT_NAWS,(byte)0,(byte)80,(byte)0,(byte)24,IAC,SE});
                    state = 1;
                }
                break;
            case (byte)TELOPT_EOR:
                break;
            case (byte)TELOPT_TTYPE:
                switch(state){
                    case 0:
                        byte nextCmd = payload[++offset];
                        flag = (nextCmd == 1 )? true : false;
                        nextCmd =payload[++offset];
                        iac(payload[++offset]);
                        if(flag){
                            state = 0;
                            sendCmd(new byte[] {IAC, SB, TELOPT_TTYPE, (byte)0,(byte)'v',(byte)'t',(byte)'1',(byte)'0',(byte)'0',IAC,SE});
                        }
                        break;
                    case 2:
                        state = 1;
                        sendCmd(new byte[] {IAC,WILL,TELOPT_TTYPE});
                        break;
                }
                break;
                
        }
        
    }
    
    /** IAC handler
     *  @param cmd
     *         the command after IAC
     */
    private void iac(byte cmd) throws ArrayIndexOutOfBoundsException{
        byte nextCmd = 0;
        switch(cmd){
            case (byte)DO:
                state = 2;
                sendCmd(new byte[]{(byte)0xFF, (byte)0xFC, (byte)0x23, (byte)0xFF, (byte)0xFC, (byte)0x27, (byte)0xFF, (byte)0xFC, (byte)0x24});
                telopt(payload[++offset]);
                break;
            case (byte)DONT:
                break;
            case (byte)WILL:
                state = 1;
                telopt(payload[++offset]);
                break;
            case (byte)WONT:
                break;
            case (byte)SB:
                state = 0;
                telopt(payload[++offset]);
                break;
            case (byte)SE:
                state = 255;
                break;
        }
        
    }
    
    public ANSI getAnsi(){
        return buf;
    }
}




